import copy
import itertools
import math
import random

import numpy as np

import Super
import Target
import archiving
import load
import config
import update

particals = config.particals  # 种群数量
MaxIter = config.MaxIter  # 最大迭代次数
dim = config.dim
e = math.e

'''狮群算法'''
'''加入单双周和起始周'''


# 随机数种子
# random.seed(1234)

def MOGRO(tasks, task_room, task_time, classrooms, classroom_unavailable_time, course_students, distribution_required,
          distribution_penalty):

    # 初始化种群
    schemesList, classroomsList, conflictsList = load.Load(tasks, task_room, task_time, classrooms,
                                                           classroom_unavailable_time,
                                                           particals,distribution_required)

    # 复制种群
    copy_schemesList = copy.deepcopy(schemesList)

    # 计算适应度
    '''适应度计算  SPF  2024.9.6'''
    fitness = Target.penalty_fun(schemesList, conflictsList, course_students, classroomsList, distribution_penalty)

    # 多目标问题pareto解！！！！！！！！！！！
    newParetofrontiers = Super.super(fitness)

    # 初始化个体最优，初始是个体最优就是狮子本身
    mine_p, RTmine_p, fitness_p = init_pbest(copy_schemesList, classroomsList, newParetofrontiers)

    # 快速非支配排序和拥挤距离排序，确定全局最优,即狮王的位置
    mine_g, fitness_g, RTmine_g = archiving.init_archive(copy_schemesList, newParetofrontiers, classroomsList)

    # 进行迭代,位置更新，然后计算适应度，更新全局最优
    for t in range(MaxIter):
        update_RTmatrix = []  # 存储此次迭代过程中修改位置后的矩阵列表
        update_schemeslist = []  # 存储迭代过程中修改课程信息后方案的列表

        # 参数设置
        l1 = ((MaxIter - t) / (MaxIter - 1)) * (2 - 1 / MaxIter) + 1 / MaxIter
        l2 = ((MaxIter - t) / (MaxIter - 1)) ** 2 * (2 - 1 / MaxIter) + 1 / MaxIter
        r1 = random.uniform(0, 1)
        r2 = random.uniform(0, 1)

        for i in range(particals):
            matrix_a = copy.deepcopy(RTmine_p[i])  # 此处应该为第t-1次的个体a的历史最佳位置
            update_scheme = copy.deepcopy(mine_p[i])
            # 生成0-1之间的随机数确定操作
            q = np.random.random()
            # 1.迁移
            if q < 1 / 3:
                # 局部搜索（向全局最优解移动）
                # 遍历所有教学任务
                A1 = 1 + l1 * (r1 - 0.5)
                C1 = 2 * r2
                for index, j in enumerate(mine_p[i]):
                    task_num = j[2]  # 教学任务号
                    # 在 当前狮子排课方案RT表中找到task_num号课程的位置，位置存储在positions_a，找开始周的教室时间片
                    positions_a = find_positions(update_scheme, task_num)
                    # 在矩阵b中找到course_num号课程的行号，列号,如果课程有多次课，那么位置应该有多个，位置列表positions
                    positions_g = find_positions(mine_g, task_num)
                    if (positions_g == positions_a):
                        continue
                    #  判断冲突
                    conflict = False
                    if positions_g[0][0] != 0:
                        conflict = load.Conflit(positions_g[0][0], positions_g[1], matrix_a)
                    # 如果没有冲突
                    if conflict:
                        # 有冲突进行下一次
                        continue
                    # 如果没有冲突的话就把教室时间片集合添加到教学任务后面
                    # 先移除教学任务中之前的教室时间片
                    update_scheme = update.updated_scheme(update_scheme, positions_g, task_num)
                    # 移除原来的教室时间片,换成新的教师时间列表
                    if positions_g[0][0] != 0:
                        matrix_a = update.updated_matrixa(matrix_a, positions_a, positions_g, task_num)

                    for conflicts in conflictsList[i][:]:
                        if conflicts[2] == task_num:
                            conflictsList[i].remove(conflicts)
            elif 1 / 3 <= q < 2 / 3:
                # 随机搜索（随机选择）开采/开发
                A2 = 2 * l2 * r1 - l2
                partical = [num for num in range(particals) if num != i]
                # matrix_b = RTmine_p[random.choice(partical)]
                mine_b_pbest = mine_p[random.choice(partical)]
                for index, j in enumerate(mine_p[i]):
                    task_num = j[2]
                    # 在矩阵a中找到task_num号课程的位置，位置存储在positions_a
                    positions_a = find_positions(update_scheme, task_num)
                    # 在狮王矩阵中找到course_num号课程的行号，列号,如果课程有多次课，那么位置应该有多个，位置列表positions
                    positions_b = find_positions(mine_b_pbest, task_num)
                    if positions_a == positions_b:
                        continue
                    # 判断矩阵a中b_row和b_col的位置是否为空,如果有多个位置，可能有的位置为空，有的位置不为空，循环遍历
                    # 逼近位置
                    conflict = False
                    if positions_b[0][0] != 0:
                        conflict = load.Conflit(positions_b[0][0], positions_b[1], matrix_a)
                    if conflict:
                        continue
                    # 当对一个课程号课程进行改变后，修改schemesList表i号排课方案中course_num课程的上课信息
                    update_scheme = update.updated_scheme(update_scheme, positions_b, task_num)
                    # 如果矩阵a中b_row和b_col的位置为空，则将矩阵a中原有task_num号课程删除，
                    # 将和b_row和b_col位置变为task_num号课程，否则重新产生一个新的task_num
                    # 删除一个矩阵a中的课程
                    if positions_b[0][0] != 0:
                        matrix_a = update.updated_matrixa(matrix_a, positions_a, positions_b, task_num)

                    for conflicts in conflictsList[i][:]:
                        if conflicts[2] == task_num:
                            conflictsList[i].remove(conflicts)
            else:
                # 全局搜索（个体根据另外两个个体位置差协作）
                partical = [num for num in range(particals) if num != i]
                index_list = random.sample(partical, 2)
                mine_c_pbest = mine_p[index_list[0]]
                mine_d_pbest = mine_p[index_list[1]]
                for index, j in enumerate(mine_p[i]):
                    task_num = j[2]
                    # 在矩阵a中找到task_num教学任务的位置，位置存储在positions_a,找开始周的教室时间片,位置有多个
                    positions_a = find_positions(update_scheme, task_num)
                    # 在矩阵matrix_c中找到task_num教学任务的位置，位置存储在positions_c，找开始周的教室时间片,位置有多个
                    positions_c = find_positions(mine_c_pbest, task_num)
                    # 在矩阵matrix_d中找到task_num教学任务的位置，位置存储在positions_c，找开始周的教室时间片,位置有多个
                    positions_d = find_positions(mine_d_pbest, task_num)
                    if positions_a == positions_c == positions_d:
                        continue
                    # 判断矩阵a中b_row和b_col的位置是否为空,如果有多个位置，可能有的位置为空，有的位置不为空，循环遍历
                    # 逼近位置
                    conflict = False
                    if positions_c[0][0] != 0:
                        conflict = load.Conflit(positions_c[0][0], positions_c[1], matrix_a)
                    if not conflict:
                        # 当对一个课程号课程进行改变后，修改schemesList表i号排课方案中course_num课程的上课信息
                        update_scheme = update.updated_scheme(update_scheme, positions_c, task_num)
                        # 如果矩阵a中b_row和b_col的位置为空，则将矩阵a中原有task_num号课程删除，
                        # 将和b_row和b_col位置变为task_num号课程，否则重新产生一个新的task_num
                        # 删除一个矩阵a中的课程
                        if positions_c[0][0] != 0:
                            matrix_a = update.updated_matrixa(matrix_a, positions_a, positions_c, task_num)

                        for conflicts in conflictsList[i][:]:
                            if conflicts[2] == task_num:
                                conflictsList[i].remove(conflicts)
                        continue
                    conflict = False
                    if positions_d[0][0] != 0:
                        conflict = load.Conflit(positions_d[0][0], positions_d[1], matrix_a)
                    if not conflict:
                        # 当对一个课程号课程进行改变后，修改schemesList表i号排课方案中course_num课程的上课信息
                        update_scheme = update.updated_scheme(update_scheme, positions_d, task_num)
                        # 如果矩阵a中b_row和b_col的位置为空，则将矩阵a中原有task_num号课程删除，
                        # 将和b_row和b_col位置变为task_num号课程，否则重新产生一个新的task_num
                        # 删除一个矩阵a中的课程
                        if positions_d[0][0] != 0:
                            matrix_a = update.updated_matrixa(matrix_a, positions_a, positions_d, task_num)

                        for conflicts in conflictsList[i][:]:
                            if conflicts[2] == task_num:
                                conflictsList[i].remove(conflicts)

            update_RTmatrix.append(matrix_a)
            update_schemeslist.append(update_scheme)

        copy_schemesList = copy.deepcopy(update_schemeslist)
        # 重新计算适应度值
        fitness___ = Target.penalty_fun(copy_schemesList, conflictsList, course_students, classrooms, distribution_penalty)
        # 快速非支配排序、拥挤度计算
        newParetofrontiers = Super.super(fitness___)
        # 更新个体最优
        # lion_p, RTlion_p, fitness_p = update.update_pbest(copy_schemesList, update_RTmatrix, fitness___,
        #                                                   lion_p, RTlion_p, fitness_p)
        # 更新全局最优(狮王位置)
        # lion_g, lion_fitness, RTlion_g = archiving.select_global_best(copy_schemesList, lion_g, lion_fitness,
        #                                                               newParetofrontiers, update_RTmatrix, RTlion_g)
        total_penalty = fitness_g[1] + fitness_g[2] + 10 * fitness_g[3]
        print("迭代次数：", t + 1)
        print("教师惩罚值：", fitness_g[1], "时间惩罚值：", fitness_g[2], "学生和约束惩罚值：", fitness_g[3], "全局最优值：", total_penalty)
    return mine_g, fitness_g


def calculate_position(task_num, position_a, position_b, taks_room, task_time, classroom_unavailable_time):
    times = [time for time in task_time if time[0] == task_num][0]
    if position_a[0][0] != 0:
        rooms = [room for room in taks_room if room[0] == task_num][0]
        rooms_times = list(itertools.product(rooms[1], times[1]))
        for room_time in rooms_times[:]:
            for un_room in classroom_unavailable_time:
                if room_time[0][0] == un_room[0]:
                    for day, start, length, week in un_room[1]:
                        if ((int(week, 2) & int(room_time[1][3], 2) == 0) or (
                                int(day, 2) & int(room_time[1][0], 2) == 0)):
                            continue
                        # 计算选择的时间和教室时间是否有重叠
                        exist_end = start + length
                        new_end = room_time[1][1] + room_time[1][2]
                        if (start < new_end and exist_end > room_time[1][1]):
                            # 删除教室时间片组合如果长度大于1的话
                            if room_time in rooms_times:
                                rooms_times.remove(room_time)
                            # rooms_times.remove(room_time)
                    break
            break
        if len(rooms_times) > 2:
            rooms_times.remove(position_a)
            rooms_times.remove(position_b)
            new_position_a = random.choice(rooms_times)
        else:
            new_position_a = position_a
    else:
        if (len(times) > 2):
            times.remove(position_a[1])
            times.remove(position_b[1])
            new_time = random.choice(times)
            new_position_a = ((0, 0), new_time)
        else:
            new_position_a = position_a
    return new_position_a


# 发现教学任务位置
def find_positions(update_scheme, element):
    position = [pos[5] for pos in update_scheme if pos[2] == element][0]
    return position


# 初始化个体最优值
def init_pbest(init_population, init_popuRT, fitness_):  # 第一次迭代最优个体就是本身
    return init_population, init_popuRT, fitness_
